package com.googlecode.goldfinger

import scala.collection.mutable.ArrayBuffer
import macromedia.asc.embedding.avmplus.ByteCodeFactory
/*
 * Generuj byte code.
 * Indexy
*/

class ActionBlock(byteCodeFactory: ByteCodeFactory) extends {  
  val minorVersion = 16
  val majorVersion = 46

  sealed class ElementStore[T](val implicitElements: Int) {
    private[this] val items = new ArrayBuffer[T]
    def add(x: T): Int = {
      if (items contains x) {
        items.indexOf(x)
      } else {
        items.append(x)
        items.length - 1
      } + implicitElements
    }
    def length = items.length + implicitElements
    def elements = items.elements
  }
  
  sealed abstract class AbcElement 

//  case class IntConstant(i: Int) extends AbcElement
//  case class UnsignedConstant(n: Nothing) extends AbcElement
//  case class DoubleConstant(d: Double) extends AbcElement
//  case class StringConstant(s: String) extends AbcElement
  case class Namespace(uri: String) extends AbcElement
  case class NamespaceSet(set: scala.collection.Set[Namespace]) extends AbcElement
  case class Multiname(name: String, nsset: NamespaceSet) extends AbcElement // QName ?
                         // TODO: implement classes for different multiname types
  case class Method(returnType: Multiname, paramTypes: List[Multiname],
               /*  
                                      IntList param_values,
                                      ByteList param_kinds,
                                      IntList param_names,  */
                                      flags: Int) extends AbcElement
  case class Metadata(name: String, data: Map[String, String]) extends AbcElement
  case class Instance(name: Multiname, superName: Multiname, flags: Byte, protectedNs: Namespace,
    interfaces: List[Multiname], iinit: Method, itraits: List[Trait]) extends AbcElement
  case class ClassInfo(cinit: Method, traits: List[Trait]) extends AbcElement
  case class Script(init: Method, traits: List[Trait]) extends AbcElement 
  case class MethodBody(method: Method, maxStack: Int, localCount: Int, initScopeDepth: Int,
    maxScopeDepth: Int, code: Bytes, exceptions: List[Exception], traits: List[Trait]) extends AbcElement

  case class Trait(name: Multiname, kind: Byte, data: Bytes, metadata: List[Metadata]) extends AbcElement
                   // TODO: implement classes for different trait kinds
  case class Exception(from: Int, to: Int, exceptionType: String, varName: String)
  
  implicit object signed extends ElementStore[Int](1) 
  implicit object unsigned extends ElementStore[Nothing](1) 
  implicit object doubles extends ElementStore[Double](1)
  implicit object strings extends ElementStore[String](1)
  implicit object namespaces extends ElementStore[Namespace](1) 
  implicit object namespaceSets extends ElementStore[NamespaceSet](1) 
  implicit object multinames extends ElementStore[Multiname](1)
  
  implicit object methods extends ElementStore[Method](0)
  implicit object metadata extends ElementStore[Metadata](0)
  implicit object instances extends ElementStore[Instance](0)
  implicit object classes extends ElementStore[ClassInfo](0)
  implicit object scripts extends ElementStore[Script](0)
  implicit object bodies extends ElementStore[MethodBody](0)
  
  import macromedia.asc.util.IntList
  import macromedia.asc.util.ByteList
  type Bytes = macromedia.asc.util.ByteList
  type Objects = macromedia.asc.util.ObjectList[Bytes]
  implicit def add[T](x: T)(implicit c: ElementStore[T]): Int = c.add(x)
  implicit def toobjlist[T](l: ElementStore[T]): Objects = {
    val o = new Objects()
    l.elements foreach { x => o.push_back(x) }
    o
  }
  implicit def tointlist[T](l: List[T])(implicit c: ElementStore[T]): IntList = {
    val o = new IntList
    l.elements foreach { x => o.push_back(x) }
    o
  }
  implicit def toobjlist[T](l: List[T]): Objects = {
    val o = new Objects
    l.elements foreach { x => o.push_back(x) }
    o
  }
  implicit def tojavasetofints[T](l: scala.collection.Set[T])(implicit c: ElementStore[T]): java.util.Set[java.lang.Integer] = {
    val o: java.util.Set[java.lang.Integer] = new java.util.HashSet[java.lang.Integer]
    l.elements foreach { x => o.add(add(x)) }
    o
  }
  
  
  val fy = byteCodeFactory
  implicit def bytecode(x: Any): Bytes = x match {
    case Namespace(uri) => fy.ConstantNamespace(uri)  
    case NamespaceSet(set) => fy.ConstantNamespaceSet(set) 
    case Multiname(name, nsset) => fy.ConstantMultiname(name, nsset, false)
    case Method(returnType, paramTypes,
               /*  
                                      IntList param_values,
                                      ByteList param_kinds,
                                      IntList param_names,  */
                                      flags) => fy.MethodInfo(new ByteList,
                                      paramTypes.length,
                                      returnType,
                                      paramTypes,
                                      new IntList,
                                      new ByteList,
                                      new IntList,
                                      0,
                                      0,
                                      0)
    case Metadata(name, data) => fy.MetadataInfo(new ByteList,
                                      name,
                                      0,
                                      new IntList,
                                      new IntList,
                                      0)
    case Instance(name, superName, flags, protectedNs, interfaces, iinit, itraits) => fy.InstanceInfo(new ByteList,
									 name,
									 superName,
									 flags,
									 protectedNs,
									 interfaces.length,
									 interfaces,
									 iinit,
									 itraits,
									 0)
    case ClassInfo(cinit, traits) => fy.ClassInfo(new Bytes, cinit, traits, 0)
    case Script(init, traits) => fy.ScriptInfo(new ByteList,
        init,
        traits,
        0)
    case MethodBody(method, maxStack, localCount, initScopeDepth,
      maxScopeDepth, code, exceptions, traits) => fy.MethodBody(new ByteList,
                                      maxStack,
                                      localCount,
                                      initScopeDepth,
                                      maxScopeDepth,
                                      code.size,
                                      code,
                                      exceptions.length,
                                      exceptions,
                                      traits,
                                      method)
    case Trait(name, kind, data, metadata) => fy.TraitInfo(new ByteList,
									 name,
									 0 /* tag */,
									 0,
									 0,
									 0,
                                     0,
                                     metadata)
    case Exception(from, to, exceptionType, varName) => assert(false) // TODO
    case f: ActionBlock => byteCodeFactory.ActionBlock(new Bytes,
									   minorVersion,
									   majorVersion,
                                       signed.length,
                                       signed,
                                       unsigned.length,
                                       unsigned,
                                       doubles.length,
                                       doubles,
                                       0, // decimal
                                       new Objects,
                                       strings.length,
                                       strings,
                                       multinames.length,
                                       multinames,
                                       namespaceSets.length,
                                       namespaceSets,
                                       namespaces.length,
                                       namespaces,
									   methods.length,
									   methods,
                                       metadata.length,
                                       metadata,
									   classes.length,
									   instances,
									   classes,
									   scripts.length,
									   scripts,
                                       bodies.length,
                                       bodies) 
    case _ => List()
  }  
  def emit = bytecode(this)
}